package com.black.utils.network.app.ftp;

import com.black.utils.commons.pool.PoolFactory;
import com.black.utils.commons.pool.PoolProperties;
import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.net.ftp.FTPClient;

import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.time.Duration;

/**
 * @author ylx
 */
@Slf4j
@Getter
@Setter
public class FTPClientFactory extends PoolFactory<FTPClient> {
    public FTPClientFactory(PoolProperties ftpProperties) {
        super(ftpProperties);
    }

    @Override
    public FTPClient make() {
        // 创建一个新的FTPClient实例
        return new FTPClient();
    }

    @Override
    public void destroy(FTPClient ftpClient) throws IOException {
        // 检查FTPClient是否连接状态，如果是，则尝试断开连接
        if (ftpClient.isConnected()) {
            ftpClient.disconnect();
        }
    }

    @Override
    public boolean validate(FTPClient ftpClient) throws IOException {
        // 测试是否连接,并且尝试发送一个空指令确认链接是否正常
        return ftpClient.isConnected() && ftpClient.sendNoOp();
    }

    @Override
    public void active(FTPClient ftpClient) throws Exception {
        // 检查FTPClient当前连接状态是否有效
        if (!ftpClient.isConnected()) {
            // 尝试重新连接并认证FTP客户端
            connectAndAuthenticate(ftpClient);
            // 配置FTP客户端，如设置传输模式等
            configureClient(ftpClient);
        }
    }

    @Override
    public void passivate(FTPClient ftpClient) throws Exception {
        // 检查FTPClient是否已连接
        if (ftpClient.isConnected()) {
            // 发送NOOP命令以检查连接是否正常
            ftpClient.noop();
        }
    }


    /**
     * 连接到FTP服务器并进行认证。
     * 本方法首先尝试连接到FTP服务器，然后使用提供的凭证进行登录。如果登录失败，
     * 将断开连接并抛出IOException。
     *
     * @param ftpClient FTP客户端实例，用于执行连接和认证操作。
     * @throws IOException 如果登录FTP服务器失败，则抛出此异常。
     */
    private void connectAndAuthenticate(FTPClient ftpClient) throws IOException {
        // 连接到FTP服务器，使用FTP属性中的IP和端口。
        ftpClient.connect(poolProperties.getIp(), poolProperties.getPort());

        // 尝试登录FTP服务器，如果登录失败，则断开连接并抛出异常。
        if (!ftpClient.login(poolProperties.getAccount(), poolProperties.getPassword())) {
            ftpClient.logout();
            ftpClient.disconnect();
            throw new IOException("FTP login failed for user: " + poolProperties.getAccount());
        }
    }


    /**
     * 配置FTP客户端的参数，以适应FTP服务器和传输需求。
     *
     * @param ftpClient 需要配置的FTP客户端实例。
     * @throws IOException 如果在配置过程中发生I/O错误。
     */
    private void configureClient(FTPClient ftpClient) throws IOException {
        // 设置文件传输类型为二进制，适用于所有类型的文件传输。
        ftpClient.setFileType(FTPClient.BINARY_FILE_TYPE);

        // 根据ftpProperties中的配置，设置控制连接的编码，以确保正确处理文件名和命令。
        ftpClient.setControlEncoding(poolProperties.getEncoding());

        // 设置客户端的默认字符集为UTF-8，以支持国际字符。
        ftpClient.setCharset(StandardCharsets.UTF_8);

        // 从ftpProperties获取超时设置，并根据需要配置客户端的超时设置。
        int timeout = poolProperties.getTimeout();
        if (timeout > 0) {
            // 设置控制连接保持活动状态的回复超时时间。
            ftpClient.setControlKeepAliveReplyTimeout(Duration.ofMillis(timeout));

            // 设置控制连接保持活动状态的超时时间。
            ftpClient.setControlKeepAliveTimeout(Duration.ofMillis(timeout));

            // 设置数据连接的超时时间，为控制连接超时时间的两倍，以确保足够的时间进行数据传输。
            ftpClient.setSoTimeout(timeout << 1);
        }
    }
}
